<?php

namespace yunj\core\builder;

use yunj\core\Config;
use yunj\core\control\import\YunjImportRow;
use yunj\core\Validate;
use yunj\core\enum\BuilderType;
use yunj\core\exception\GeneralException;
use yunj\core\validate\YunjImportValidate;

final class YunjImport extends Builder {

    protected $type = BuilderType::IMPORT;

    protected $builderValidateClass = YunjImportValidate::class;

    protected $scriptFileList = ["/static/yunj/js/import.min.js?v=" . YUNJ_VERSION];

    protected $options = ["sheet", "cols", "validate", 'templet', "limit", "tips", "row", "rows"];

    /**
     * 工作表
     * "sheet"=>["Sheet1","Sheet2"]
     * @var array
     */
    private $sheet;

    /**
     * 工作表表头
     * @var array<col-key,col-args>|array<sheet,array<col-key,col-args>>
     * 示例：sheet没有设置时   array<col-key,col-args>
     * 'cols'=>[
     *      "name"=>[
     *          "title"=>"姓名",
     *          "default"=>"小王",
     *          "verify"=>"require|chs",
     *          "desc"=>"必填，只能输入汉字",
     *      ],...
     *  ]
     * 示例：sheet设置时    array<sheet,array<col-key,col-args>>
     * "cols"=>[
     *      "Sheet1"=>[
     *          "name"=>[
     *              "title"=>"姓名",
     *              "default"=>"小王",
     *              "verify"=>"require|chs",
     *              "desc"=>"必填，只能输入汉字",
     *          ],...
     *      ],...
     * ]
     */
    private $cols;

    /**
     * 指定模板下载地址
     * "url"=>"http://xxx.com/xxx.xlsx"
     * @var string
     */
    private $templet;

    /**
     * 每次导入条数，默认20条
     * "limit"=>20
     * @var int
     */
    private $limit = 20;

    /**
     * 提示语
     * "tips"=>[
     *      "提示一",
     *      "提示二",
     * ]
     * @var array
     */
    private $tips = [];

    // 单行数据处理回调
    private $rowCallback;

    // 多行数据处理回调
    private $rowsCallback;

    protected function setAttr(array $args = []): void {
        parent::setAttr($args);
        $this->config = Config::get('import.', []);
    }

    /**
     * Notes: 工作表
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:21
     * @param callable|array<string>|...string $sheet
     * callable:
     *      function(){
     *          return ["Sheet1","Sheet2"];
     *      }
     * array<string>:
     *      ["Sheet1","Sheet2"];
     * ...string:
     *      "Sheet1","Sheet2"
     * @return YunjImport
     */
    public function sheet($sheet) {
        if ($this->existError()) return $this;
        if (!$sheet) return $this;
        $sheet0 = $sheet[0];
        if (is_callable($sheet0)) {
            $callable = $sheet0;
        } elseif (is_array($sheet0)) {
            $callable = function () use ($sheet0) {
                return $sheet0;
            };
        } else {
            $callable = function () use ($sheet) {
                return $sheet;
            };
        }
        $this->setOptionCallbale('sheet', $callable);
        return $this;
    }

    /**
     * Notes: 执行工作表回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_sheet_callable(callable $callable) {
        if ($this->existError()) return $this;
        $this->sheet = $this->sheet ?: [];
        $sheet = $callable($this);
        if (!is_array($sheet)) return $this->error("YunjForm [sheet] 需返回有效数组");
        $this->sheet = array_keys(array_flip($this->sheet) + array_flip($sheet));
        return $this;
    }

    /**
     * Notes: 判断是否设置sheet
     * Author: Uncle-L
     * Date: 2022/3/8
     * Time: 17:30
     * @return bool
     */
    public function isSetSheet(): bool {
        $this->execOptionsCallbale("sheet");
        return $this->sheet && count($this->sheet) > 0;
    }

    /**
     * Notes: 工作表表头
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:23
     * @param callable|array<col-key,col-args> $cols
     * @return YunjImport
     */
    public function cols($cols) {
        if ($this->existError()) return $this;
        if (!is_callable($cols)) {
            $callable = function () use ($cols) {
                return $cols;
            };
        } else {
            $callable = $cols;
        }
        $this->setOptionCallbale('cols', $callable);
        return $this;
    }

    /**
     * Notes: 执行工作表表头回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_cols_callable(callable $callable) {
        if ($this->existError()) return $this;
        $sheets = $this->sheet;
        $cols = $this->cols ?: [];
        if ($sheets) {
            foreach ($sheets as $sheet) {
                $sheetCols = $callable($this, $sheet);
                if (!is_array($sheetCols)) return $this->error("YunjImport [cols] 需返回有效数组");
                $cols[$sheet] = isset($cols[$sheet]) ? $cols[$sheet] : [];
                foreach ($sheetCols as $key => $args) {
                    $cols[$sheet][$key] = $args + ["title" => "", "default" => "", "verify" => "", "desc" => ""];
                }
            }
        } else {
            $_cols = $callable($this, null);
            if (!is_array($_cols)) return $this->error("YunjImport [cols] 需返回有效数组");
            foreach ($_cols as $key => $args) {
                $cols[$key] = $args + ["title" => "", "default" => "", "verify" => "", "desc" => ""];
            }
        }
        $this->cols = $cols;
        return $this;
    }

    /**
     * Notes: 指定模板下载地址
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:41
     * @param callable|string $templet
     * callable:
     *      function(){
     *          return "xxxx.com/xxx.xlsx";
     *      }
     * string:
     *      xxxx.com/xxx.xlsx
     * @return YunjImport
     */
    public function templet($templet) {
        if ($this->existError()) return $this;
        if (!is_callable($templet)) {
            $callable = function () use ($templet) {
                return $templet;
            };
        } else {
            $callable = $templet;
        }
        $this->setOptionCallbale('templet', $callable);
        return $this;
    }

    /**
     * Notes: 执行指定模板下载地址回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_templet_callable(callable $callable) {
        if ($this->existError()) return $this;
        $templet = $callable($this);
        if (!is_string($templet)) return $this->error("YunjImport [templet] 需返回有效地址");
        $this->templet = $templet;
        return $this;
    }

    /**
     * Notes: 每次导入条数
     * Author: Uncle-L
     * Date: 2021/4/13
     * Time: 19:40
     * @param callable|int $limit
     * callable:
     *      function(){
     *          return 20;
     *      }
     * int:
     *      20
     * @return YunjImport
     */
    public function limit($limit) {
        if ($this->existError()) return $this;
        if (!is_callable($limit)) {
            $callable = function () use ($limit) {
                return $limit;
            };
        } else {
            $callable = $limit;
        }
        $this->setOptionCallbale('limit', $callable);
        return $this;
    }

    /**
     * Notes: 执行每次导入条数回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_limit_callable(callable $callable) {
        if ($this->existError()) return $this;
        $limit = $callable($this);
        if (!is_positive_integer($limit)) return $this->error("YunjImport [limit] 需返回正整数");
        $this->limit = $limit;
        return $this;
    }

    /**
     * Notes: 提示语
     * Author: Uncle-L
     * Date: 2021/4/10
     * Time: 17:22
     * @param callable|array<string>|...string $tips
     * callable:
     *      function(){
     *          return ["提示一","提示二"];
     *      }
     * array<string>:
     *      ["提示一","提示二"]
     * ...string:
     *      "提示一","提示二"
     * @return YunjImport
     */
    public function tips(...$tips) {
        if ($this->existError()) return $this;
        if (!$tips) return $this;
        $tip0 = $tips[0];
        if (is_callable($tip0)) {
            $callable = $tip0;
        } elseif (is_array($tip0)) {
            $callable = function () use ($tip0) {
                return $tip0;
            };
        } else {
            $callable = function () use ($tips) {
                return $tips;
            };
        }
        $this->setOptionCallbale('tips', $callable);
        return $this;
    }

    /**
     * Notes: 执行提示语回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjImport
     */
    protected function exec_tips_callable(callable $callable) {
        if ($this->existError()) return $this;
        $this->tips = $this->tips ?: [];
        $tips = $callable($this);
        if (!is_array($tips) || !array_depth($tips, 1)) return $this->error("YunjImport [tips] 需返回一维数组");
        $this->tips = array_keys(array_flip($this->tips) + array_flip($tips));
        return $this;
    }

    /**
     * Notes: 单行数据处理
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjImport
     */
    public function row(callable $callable) {
        if ($this->existError()) return $this;
        $this->rowCallback = $callable;
        return $this;
    }

    /**
     * Notes: 多行数据处理
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:04
     * @param callable $callable
     * @return YunjImport
     */
    public function rows(callable $callable) {
        if ($this->existError()) return $this;
        $this->rowsCallback = $callable;
        return $this;
    }

    /**
     * @return array
     * @throws GeneralException
     */
    public function viewArgs(): array {
        $args = parent::viewArgs();
        if ($this->sheet) {
            $args['sheet'] = $this->sheet;
        }
        if ($this->cols) {
            $args['cols'] = $this->cols;
        }
        if ($this->templet) {
            $args['templet'] = $this->templet;
        }
        $args['limit'] = $this->limit;
        $args['tips'] = array_keys(array_flip($this->config["tips"]) + array_flip($this->tips));
        return $args;
    }

    /**
     * 获取表头字段
     * @return array[]|array[][]
     */
    public function getSheet(): array {
        if (is_null($this->sheet)) {
            $this->execOptionsCallbale("sheet");
            if ($this->existError()) throw_error_json($this->getError());
        }
        return $this->sheet ?? [];
    }

    /**
     * 获取表头字段
     * @param null $sheet
     * @return array|col-args[]|col-args[][]
     */
    public function getCols($sheet = null): array {
        if (is_null($this->cols)) {
            $this->execOptionsCallbale("sheet,cols");
            if ($this->existError()) throw_error_json($this->getError());
        }
        if (!$this->cols) {
            return [];
        }
        return $sheet ? ($this->cols[$sheet] ?? []) : $this->cols;
    }

    /**
     * Notes: 异步数据导入
     * Author: Uncle-L
     * Date: 2021/10/12
     * Time: 16:34
     * @param array $data
     * @return array
     */
    protected function async_import(array $data) {
        $this->execOptionsCallbale("sheet,cols,limit,row,rows");
        if ($this->existError()) throw_error_json($this->getError());
        if (!$this->rowCallback && !$this->rowsCallback) throw_error_json("YunjImport [row、rows] 未设置");
        if ($data["count"] > $this->limit) throw_error_json("请求数量超过限制{$this->limit}");
        $items = $data['items'];
        $rows = [];
        foreach ($items as $item) {
            $sheet = $item['_sheet'];
            $row = new YunjImportRow($this, $item);
            // 设置有单行数据处理
            if ($this->rowCallback && $row->isCanImport()) {
                call_user_func_array($this->rowCallback, [$this, $row]);
                if ($row->isCanImport()) {
                    // 未修改状态时自动修改状态
                    $row->importSuccess();
                }
            }
            $rows[] = $row;
        }

        // 设置有多行数据处理
        if ($this->rowsCallback) {
            // 拿到可导入数据
            $_rows = [];
            foreach ($rows as $row) {
                if ($row->isCanImport()) {
                    $_rows[] = $row;
                }
            }
            call_user_func_array($this->rowsCallback, [$this, $_rows]);
        }

        // 处理响应状态
        $resItems = [];
        foreach ($rows as $row) {
            if ($row->isCanImport()) {
                // 未修改状态的数据默认为成功
                $row->importSuccess();
            }
            $resItems[] = $row->responseData();
        }

        $response = [
            'limit' => $this->limit,
            'count' => $data['count'],
            'items' => $resItems
        ];
        return $response;


//        $passItems = [];
//        $passIdKeyMap = [];
//        $isSetSheet = $this->isSetSheet();
//        foreach ($items as $k => $v) {
//            $rowObj = new YunjImportRow($this, $v);
//            $error = $this->validateCheck($v, 'Row');
//            $rowObj->setItemData($this->getValidate()->getData());
//            if ($error !== '') {
//                $rowObj->setError($error);
//                $v['_rowObj'] = $rowObj;
//                continue;
//            }
//            if ($this->rowCallback) {
//                call_user_func_array($this->rowCallback, [$this, $rowObj]);
//                if ($rowObj->isCanImport()) {
//                    // 未修改状态时自动修改状态
//                    $rowObj->importSuccess();
//                }
//                $v['_rowObj'] = $rowObj;
//                continue;
//            }

//            $sheet = $isSetSheet ? $v['sheet'] : null;
//            $id = $v['id'];
//            $row = $v['row'];
//            if (!is_null($sheet)) $row['sheet'] = $sheet;
//            $res = $this->validateCheck($row, 'Row');
//            if ($res !== '') {
//                $items[$k]["_importStatus"]["code"] = "fail";
//                $items[$k]["_importStatus"]["desc"] = "上传失败！" . $res;
//                continue;
//            }
//            if ($this->rowCallback) {
//                $res = call_user_func_array($this->rowCallback, [$this, $sheet, $row]);
//                $this->setImportItemState($items[$k], $res === true ? null : (is_string($res) ? $res : ""));
//                continue;
//            }
//            $v["row"] = $row;
//            $passItems[$id] = $v;
//            $passIdKeyMap[$id] = $k;
//        }
//        if ($passItems && $this->rowsCallback) {
//            $error = call_user_func_array($this->rowsCallback, [$this, $passItems]);
//            foreach ($passItems as $id => $item) {
//                $idx = $passIdKeyMap[$id];
//                $itemError = is_string($error) ? $error : (is_array($error) && array_key_exists($id, $error) && is_string($error[$id]) ? $error[$id] : null);
//                $this->setImportItemState($items[$idx], $itemError);
//            }
//        }
//        $response = [
//            'limit' => $this->limit,
//            'count' => $data['count'],
//            'items' => $items
//        ];
//        return $response;
    }

    /**
     * 设置导入数据状态
     * @param array $item
     * @param string|null $error 错误描述，空默认为成功
     */
    private function setImportItemState(array &$item, string $error = null): void {
        if (is_string($error)) {
            $code = "fail";
            $tips = "上传失败！" . ($error ? ":{$error}" : "");
        } else {
            $code = "success";
            $tips = "上传成功";
        }
        $item["_importStatus"]["code"] = $code;
        $item["_importStatus"]["desc"] = $tips;
    }

    /**
     * 验证器校验数据
     * @param array $data 待验证数据
     * @param string $scene
     * @return string   错误信息
     */
    private function validateCheck(array &$data, string $scene): string {
        if (!$data) return '';
        // 单行数据校验
        if ($scene == 'Row') {
            $sheet = $data['_sheet'] ?? null;
            $validateArgs = $this->getValidateCheckArgsByRow($sheet);
            if (is_string($validateArgs)) return $validateArgs;
            [$rule, $field] = $validateArgs;
            if (!$rule) return '';
            $err = parent::validateCheckByNormal($rule, $field, $data, $scene, false);
            return $err ?: '';
        }
    }

    /**
     * 获取验证器验证参数
     * @param string|mixed $sheet
     * @return array|mixed
     */
    private function getValidateArgsByRow($sheet = null) {
        static $args;
        $args = $args ?: [];

        if ($this->isSetSheet()) {
            if (!$sheet) {
                return "工作簿{$sheet}在导入模板中不存在";
            }
        }

        if ($sheet) {
            if ($args && isset($args[$sheet])) return $args[$sheet];
            if (!in_array($sheet, $this->sheet)) return "工作簿{$sheet}在导入模板中不存在";
            if (!($cols = $this->getCols($sheet))) return "没有设置工作簿{$sheet}表头配置";
        } else {
            if ($args) return $args;
            $cols = $this->getCols();
        }
        $rule = [];
        $field = [];
        foreach ($cols as $ck => $cv) {
            if (!isset($cv['verify']) || !$cv['verify']) continue;
            $rule[$ck] = $cv["verify"];
            $field[$ck] = $cv['title'] ?: $ck;
        }
        $colsArgs = [$rule, $field];
        if ($sheet) {
            $args[$sheet] = $colsArgs;
        } else {
            $args = $colsArgs;
        }
        return $colsArgs;
    }

}